package org.xx.armory.rpc.decorators;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.config.ConfigurationException;
import org.xx.armory.config.ConfigurationManager;
import org.xx.armory.config.data.RedisSection;
import org.xx.armory.db.JedisPoolFactory;
import org.xx.armory.rpc.Decorator;
import org.xx.armory.security.AuthenticationToken;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

public final class RedisAuthenticationDecorator
        extends AbstractAuthenticationDecorator
        implements Decorator {
    private static final String TABLE_NAME = "armory.token";
    private static final String DEFAULT_APP_NAME = "app";
    private final Logger logger = LoggerFactory.getLogger(RedisAuthenticationDecorator.class);
    private String appName;
    private String dataSourceName;
    private JedisPool pool;

    public RedisAuthenticationDecorator() {
        this.appName = DEFAULT_APP_NAME;
        this.pool = null;
    }

    /**
     * 根据令牌ID获取完整的Key。
     * <p>通过表名、应用程序名和令牌ID组合完整的Key。</p>
     *
     * @param tokenId
     *         令牌ID。
     * @return 组合后完整的Key。
     */
    private String key(
            String tokenId
    ) {
        return TABLE_NAME + ":" + DEFAULT_APP_NAME + "." + tokenId;
    }

    private String keysPattern() {
        return TABLE_NAME + ":" + DEFAULT_APP_NAME + ".*";
    }

    /**
     * 获取应用程序的名字。
     *
     * @return 应用程序的名字。
     */
    public String getAppName() {
        return appName;
    }

    /**
     * 设置应用程序的名字。
     *
     * @param appName
     *         应用程序的名字，自动去掉首尾空格。
     * @throws IllegalArgumentException
     *         如果参数{@code appName}是{@code null}或者只包含空白字符。
     * @throws IllegalStateException
     *         如果此修饰器已初始化。
     */
    public void setAppName(
            String appName
    ) {
        assertNotInitialized();

        this.appName = appName;
    }

    /**
     * 获取使用的Redis数据源的名字。
     *
     * @return 使用的Redis数据源的名字。
     */
    public final String getDataSourceName() {
        return dataSourceName;
    }

    /**
     * 设置使用的Redis数据源的名字。
     *
     * @param dataSourceName
     *         使用的Redis数据源的名字，自动去掉首尾空格。
     * @throws IllegalArgumentException
     *         如果参数{@code dataSourceName}是{@code null}或者只包含空白字符。
     * @throws IllegalStateException
     *         如果此修饰器已初始化。
     */
    public final void setDataSourceName(
            String dataSourceName
    ) {
        assertNotInitialized();

        this.dataSourceName = notBlank(dataSourceName, "dataSourceName").trim();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initialize()
            throws Exception {
        super.initialize();

        final RedisSection redisSection = ConfigurationManager.getConfiguration().getData().getRedis(this.getDataSourceName());
        if (redisSection == null) {
            throw new ConfigurationException("cannot find redis data source: " + this.getDataSourceName());
        }
        this.pool = JedisPoolFactory.createPool(redisSection.getUri(), redisSection.getTimeout());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void dispose()
            throws Exception {
        this.pool.close();

        super.dispose();
    }

    /**
     * 获取Redis连接池。
     *
     * @return Redis连接池。
     */
    protected JedisPool getPool() {
        return this.pool;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected final AuthenticationToken loadToken(
            String tokenId
    ) {
        tokenId = notBlank(tokenId, "tokenId").trim();

        try (final Jedis jedis = getPool().getResource()) {
            final String key = key(tokenId);
            logger.trace("load token from redis(key={})", key);

            AuthenticationToken token = mapToToken(jedis.hgetAll(key));

            if (token != null) {
                logger.trace("loaded token {} from redis(key={})", token, key);
            } else {
                token = AuthenticationToken.guest();
                logger.trace("null loaded, use guest token {} instead redis(key={})", token, key);
            }

            if (!token.isPersist()) {
                // 对于非持久保存的令牌，设置自动过期时间。
                jedis.expire(key, getSlide());

                logger.trace("expire redis(key={}) after {} seconds", key, getSlide());
            }

            return token;
        } catch (Exception ex) {
            throw new IllegalStateException("cannot load token", ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void saveToken(
            String tokenId,
            AuthenticationToken token
    ) {
        tokenId = notBlank(tokenId, "tokenId").trim();

        try (final Jedis jedis = getPool().getResource()) {
            final String key = key(tokenId);
            if (token != null) {
                logger.trace("save token {} to redis(key={})", token, key);

                jedis.hmset(key, tokenToMap(token));
            } else {
                logger.trace("delete existed token from redis(key={})", key);

                jedis.del(key);
            }
        } catch (Exception ex) {
            throw new IllegalStateException("cannot save token", ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void signIn(
            AuthenticationToken token
    ) {
        notNull(token, "token");

//        try (final Jedis jedis = getPool().getResource()) {
//            List<String> removedKeys = new ArrayList<>();
//
//            ScanParams scanParams = new ScanParams().count(100).match(keysPattern());
//            String cursor = ScanParams.SCAN_POINTER_START;
//            boolean scanFinished = false;
//            while (!scanFinished) {
//                ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
//                scanResult.getResult()
//                          .stream()
//                          .filter(key -> {
//                              final String userId = jedis.hget(key, UID_KEY_NAME);
//                              return Objects.equals(token.getUserId(), userId);
//                          })
//                          .forEach(removedKeys::add);
//                cursor = scanResult.getStringCursor();
//                if ("0".equals(cursor)) {
//                    scanFinished = true;
//                }
//            }
//
//            logger.trace("{} repetitive token id found", removedKeys.size());
//
//            removedKeys.forEach(key -> {
//                logger.trace("delete repetitive token {}", key);
//
//                jedis.del(key);
//            });
//        } catch (Exception ex) {
//            throw new IllegalStateException("cannot scan repetitive token(s)", ex);
//        }
    }
}
